package com.foxsteps.gsonformat.component.operation.impl;

import com.foxsteps.gsonformat.apiutils.PsiAnnotationUtils;
import com.foxsteps.gsonformat.component.operation.AnnotionDocWriter;
import com.foxsteps.gsonformat.entity.MapStructAutoModel;
import com.foxsteps.gsonformat.enums.AnnotationClass;
import com.foxsteps.gsonformat.enums.MapstructModeEnum;
import com.foxsteps.gsonformat.exception.FileNotValidException;
import com.foxsteps.gsonformat.exception.NotFoundElementException;
import com.foxsteps.gsonformat.model.DialogModel;
import com.foxsteps.gsonformat.model.RangeHighlightModel;
import com.foxsteps.gsonformat.utils.PsiElementUtils;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.ReadonlyStatusHandler.OperationStatus;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.util.PsiEditorUtil;
import com.intellij.ui.JBColor;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.util.*;
import java.util.List;

/**
 * The type Java doc writer impl.
 *
 * @author Sergey Timofiychuk
 */
public class AnnotionDocWriterImpl implements AnnotionDocWriter {

    private static final Logger LOGGER = Logger.getInstance(AnnotionDocWriterImpl.class);


    @Override
    public void write(DialogModel model, @NotNull PsiAnnotation javaDoc, @NotNull PsiElement beforeElement) {
        WriteCommandAction.writeCommandAction(beforeElement.getProject(), beforeElement.getContainingFile()).withName(WRITE_JAVADOC_COMMAND_NAME)
                .withGroupId(WRITE_JAVADOC_COMMAND_GROUP).run(()->{
            try {
                checkFilesAccess(beforeElement);
            } catch (FileNotValidException e) {
                LOGGER.error(e.getMessage());
                Messages.showErrorDialog("apiDoc plugin is not available, cause: " + e.getMessage(), "apiDoc plugin");
                return;
            }
            if (javaDoc == null) {
                return;
            }
            if(MapstructModeEnum.DEMO.getCode().equals(model.getMode())){
                addDemoJavaDoc(beforeElement,javaDoc);
            }else {
                PsiElement psiElement = beforeElement.getFirstChild();
                PsiElement mapStructAnnotation = PsiElementUtils.checkReplaceJavaDoc(beforeElement);
                if(Objects.nonNull(mapStructAnnotation)){
                    replaceJavaDoc(beforeElement,mapStructAnnotation, javaDoc);
                }else {
                    addJavaDoc(beforeElement,javaDoc);
                }
            }
            ensureWhitespaceAfterJavaDoc(beforeElement);
            reformatJavaDoc(beforeElement);
            reformatJavaDoc(beforeElement);
            highlightGrammar(beforeElement);
        });
    }

    private void highlightGrammar(PsiElement theElement) {
        FileEditorManager editor = FileEditorManager.getInstance(theElement.getProject());

        HighlightManager highlightManager = HighlightManager.getInstance(theElement.getProject());
        TextAttributes attributes = new TextAttributes(null, JBColor.lightGray,
                JBColor.YELLOW, EffectType.BOXED, Font.PLAIN);
        PsiElement annotation = PsiElementUtils.checkReplaceJavaDoc(theElement);
        if(Objects.isNull(annotation)){
            return;
        }
        Map<String, List<RangeHighlightModel>> sourceHighlightModelMap = new HashMap<>();
        Map<String, List<RangeHighlightModel>> targetHighlightModelMap = new HashMap<>();
        try {
            for(PsiElement subAnnotation : annotation.getChildren()){
                if(!(subAnnotation instanceof PsiAnnotationParameterList)){
                    continue;
                }
                PsiAnnotationParameterList psiAnnotationParameterList = (PsiAnnotationParameterList) subAnnotation;
                PsiNameValuePair[] psiNameValuePairs = psiAnnotationParameterList.getAttributes();
                for(PsiNameValuePair pair : psiNameValuePairs){
                    PsiAnnotationMemberValue psiAnnotationMemberValue = pair.getValue();
                    for(PsiElement sub : psiAnnotationMemberValue.getChildren()){
                        if(!(sub instanceof PsiAnnotation)){
                            continue;
                        }
                        PsiAnnotation psiAnnotation = (PsiAnnotation) sub;
                        boolean checkFlag = PsiAnnotationUtils.INSTANCE.isAssignableFrom(AnnotationClass.MASTRUCT_MAPPING_DETAIL.getClassName(), psiAnnotation);
                        if(!checkFlag){
                            continue;
                        }
                        for(PsiElement psiElement1 : psiAnnotation.getChildren()){
                            if(!(psiElement1 instanceof PsiAnnotationParameterList)){
                                continue;
                            }
                            MapStructAutoModel mapStructAutoModel = new MapStructAutoModel();
                            PsiAnnotationParameterList annotationParameterList = (PsiAnnotationParameterList) psiElement1;
                            PsiNameValuePair[] pairs = annotationParameterList.getAttributes();
                            for(PsiNameValuePair pair1 : pairs){
                                int startOffset = pair1.getTextOffset();
                                int endOffset = pair1.getTextOffset() + pair1.getTextLength();
                                String attributeName = pair1.getAttributeName();
                                String attributeValue = pair1.getValue().getText().replaceAll("\"","");
                                if(("source".equals(attributeName))){
                                    List<RangeHighlightModel> sourceModeList = sourceHighlightModelMap.get(attributeValue);
                                    if(Objects.isNull(sourceModeList)){
                                        sourceModeList = Lists.newArrayList();
                                        sourceHighlightModelMap.put(attributeValue,sourceModeList);
                                    }
                                    sourceModeList.add(new RangeHighlightModel(pair1.getTextOffset(),endOffset));
                                }
                                if(("target".equals(pair1.getAttributeName()))){
                                    List<RangeHighlightModel> targetModeList = targetHighlightModelMap.get(attributeValue);
                                    if(Objects.isNull(targetModeList)){
                                        targetModeList = Lists.newArrayList();
                                        targetHighlightModelMap.put(attributeValue,targetModeList);
                                    }
                                    targetModeList.add(new RangeHighlightModel(startOffset,endOffset));
                                }
                            }
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        if(sourceHighlightModelMap.size() > 0){
            for(Map.Entry<String, List<RangeHighlightModel>> entry : sourceHighlightModelMap.entrySet()){
                List<RangeHighlightModel> rangeHighlightModels = entry.getValue();
                if(Objects.isNull(rangeHighlightModels) || rangeHighlightModels.size() < 2){
                    continue;
                }
                for(RangeHighlightModel rangeHighlightModel : rangeHighlightModels){
                    highlightManager.addRangeHighlight(Objects.requireNonNull(editor.getSelectedTextEditor()),rangeHighlightModel.getStartOffset(),rangeHighlightModel.getEndOffset(),
                            attributes,false,false,null);
                }
            }
        }
        if(targetHighlightModelMap.size() > 0){
            for(Map.Entry<String, List<RangeHighlightModel>> entry : targetHighlightModelMap.entrySet()){
                List<RangeHighlightModel> rangeHighlightModels = entry.getValue();
                if(Objects.isNull(rangeHighlightModels) || rangeHighlightModels.size() < 2){
                    continue;
                }
                for(RangeHighlightModel rangeHighlightModel : rangeHighlightModels){
                    highlightManager.addRangeHighlight(Objects.requireNonNull(editor.getSelectedTextEditor()),rangeHighlightModel.getStartOffset(),rangeHighlightModel.getEndOffset(),
                            attributes,false,false,null);
                }
            }
        }
    }

    private void addDemoJavaDoc(PsiElement theElement, PsiAnnotation javaDoc) {
        pushPostponedChanges(theElement);
        theElement.getNode().addChild(javaDoc.getNode());
    }

    private boolean checkMoreAnnotation(PsiElement psiElement) {
        try {
            if(psiElement instanceof PsiModifierList){
                PsiModifierList psiModifierList = (PsiModifierList) psiElement;
                return psiModifierList.getChildren().length > 1;
            }
        }catch (Exception e){

        }
        return false;
    }

    private void handlerImport(PsiElement beforeElement) {
//        PsiClass psiClass = new PsiClassImpl();
//        PsiImportStatement psiImportStatement = JavaPsiFacade.getElementFactory(beforeElement.getProject()).createImportStatement("com.df.df.er.s");
//        beforeElement.getNode().addChild(psiImportStatement.getNode(), beforeElement.getFirstChild().getNode());
    }

    private void replaceJavaDoc(@NotNull PsiElement beforeElement, PsiElement handlerElement, PsiAnnotation javaDoc) {
        PsiElement psiElement = handlerElement.getParent();
        deleteJavaDoc(handlerElement);
        psiElement.getNode().addChild(javaDoc.getNode(), Optional.ofNullable(psiElement.getFirstChild()).map(PsiElement::getNode).orElse(null));
    }

    private void updateJavaDoc(PsiElement beforeElement, PsiElement nextSibling, PsiAnnotation javaDoc) {
        pushPostponedChanges(beforeElement);
        beforeElement.getNode().addChild(javaDoc.getNode(), nextSibling.getNode());
    }

    private static void deleteJavaDoc(PsiElement theElement) {
        pushPostponedChanges(theElement);
        theElement.delete();
    }

    private void addJavaDoc(PsiElement theElement, PsiAnnotation javaDoc) {
        pushPostponedChanges(theElement);
        PsiElement psiElement = theElement.getFirstChild();
        if (Objects.nonNull(psiElement) && psiElement instanceof PsiDocComment) {
            theElement.getNode().addChild(javaDoc.getNode(), theElement.getFirstChild().getNextSibling().getNode());
        }else {
            theElement.getNode().addChild(javaDoc.getNode(), theElement.getFirstChild().getNode());
        }
    }

    private static void pushPostponedChanges(PsiElement element) {
        Editor editor = PsiEditorUtil.Service.getInstance().findEditorByPsiElement(element);
        if (editor != null) {
            PsiDocumentManager.getInstance(element.getProject())
                    .doPostponedOperationsAndUnblockDocument(editor.getDocument());
        }
    }

    private void checkFilesAccess(@NotNull PsiElement beforeElement) {
        PsiFile containingFile = beforeElement.getContainingFile();
        if (containingFile == null || !containingFile.isValid()) {
            throw new FileNotValidException("File cannot be used to generate javadocs");
        }
        OperationStatus status = ReadonlyStatusHandler.getInstance(beforeElement.getProject()).
                ensureFilesWritable(Collections.singletonList(containingFile.getVirtualFile()));
        if (status.hasReadonlyFiles()) {
            throw new FileNotValidException(status.getReadonlyFilesMessage());
        }
    }

    private void ensureWhitespaceAfterJavaDoc(PsiElement element) {
        // this method is required to create well formatted javadocs in enums
        PsiElement firstChild = element.getFirstChild();
        if (!PsiAnnotation.class.isAssignableFrom(firstChild.getClass())) {
            return;
        }
        PsiElement nextElement = firstChild.getNextSibling();
        if (PsiWhiteSpace.class.isAssignableFrom(nextElement.getClass())) {
            return;
        }
        pushPostponedChanges(element);
        element.getNode().addChild(new PsiWhiteSpaceImpl("\n"), nextElement.getNode());
    }

    private static void reformatJavaDoc(PsiElement theElement) {
        CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(theElement.getProject());
        pushPostponedChanges(theElement);
        try {
            int javadocTextOffset = findJavaDocTextOffset(theElement);
            int javaCodeTextOffset = findJavaCodeTextOffset(theElement);
            codeStyleManager.reformatText(theElement.getContainingFile(), javadocTextOffset, javaCodeTextOffset + 1);
        } catch (NotFoundElementException e) {
            LOGGER.info("Could not reformat javadoc since cannot find required elements", e);
        }
    }

    private static int findJavaDocTextOffset(PsiElement theElement) {
        PsiElement javadocElement = theElement.getFirstChild();
        return javadocElement.getTextOffset();
    }

    private static int findJavaCodeTextOffset(PsiElement theElement) {
        return theElement.getChildren()[theElement.getChildren().length - 1].getTextOffset();
    }
}
